
#include <windows.h>
#include <stdio.h>
//#include <stdexcept>

#include "Service.h"
#include "Log.h"
#include "config.h"
#include "control.h"

namespace wrservice
{

/////////////////////////////////////////////////////////////////////
//
// Goal : 
//   Starts the service if possible.
//
void service_start (Service& svc)
{
    SC_HANDLE               schSCManager;
    SC_HANDLE               schService;
    SERVICE_STATUS_PROCESS  ssStatus; 
    DWORD   dwOldCheckPoint; 
    DWORD   dwStartTickCount;
    DWORD   dwWaitTime;
    DWORD   dwBytesNeeded;

    schSCManager = OpenSCManager (
        NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (NULL == schSCManager) {
        svc.get_log() << "OpenSCManager failed";
        //throw std::runtime_error ("OpenSCManager failed");
        return;
    }

    schService = OpenService (
        schSCManager, svc.get_name(), SERVICE_ALL_ACCESS);
    if (schService == NULL)
    {
        svc.get_log() << "OpenService failed";
        //throw std::runtime_error ("OpenService failed");
        CloseServiceHandle(schSCManager);
        return;
    } 

    // Check the status in case the service is not stopped.
    if (!QueryServiceStatusEx ( 
            schService,                     // handle to service 
            SC_STATUS_PROCESS_INFO,         // information level
            (LPBYTE) &ssStatus,             // address of structure
            sizeof(SERVICE_STATUS_PROCESS), // size of structure
            &dwBytesNeeded )                // size needed if buffer is too small
       )
    {
        svc.get_log() << "QueryServiceStatusEx failed";
        //throw std::runtime_error ("QueryServiceStatusEx failed");
        goto cleanup;
    }

    // Check if the service is already running
    if ( ssStatus.dwCurrentState != SERVICE_STOPPED && 
         ssStatus.dwCurrentState != SERVICE_STOP_PENDING )
    {
        svc.get_log().write_event(
            "Cannot start the service because it is already running");
        goto cleanup;
    }

    // Save the tick count and initial checkpoint.
    dwStartTickCount = GetTickCount();
    dwOldCheckPoint  = ssStatus.dwCheckPoint;

    // Wait to stop before attempting to start it.
    while (ssStatus.dwCurrentState == SERVICE_STOP_PENDING)
    {
        dwWaitTime = ssStatus.dwWaitHint / 10;

        if( dwWaitTime < 1000 )
            dwWaitTime = 1000;
        else if ( dwWaitTime > 10000 )
            dwWaitTime = 10000;

        Sleep( dwWaitTime );

        // Check the status until the service is no longer stop pending. 
        if ( !QueryServiceStatusEx( 
                schService,
                SC_STATUS_PROCESS_INFO,
                (LPBYTE) &ssStatus,
                sizeof(SERVICE_STATUS_PROCESS),
                &dwBytesNeeded )
           )
        {
            svc.get_log() << "QueryServiceStatusEx failed";
            //throw std::runtime_error ("QueryServiceStatusEx failed");
            goto cleanup;
        }

        if ( ssStatus.dwCheckPoint > dwOldCheckPoint )
        {
            dwStartTickCount = GetTickCount();
            dwOldCheckPoint = ssStatus.dwCheckPoint;
        }
        else {
            if ( GetTickCount()-dwStartTickCount > ssStatus.dwWaitHint ) {
                svc.get_log().write_event("Timeout waiting for service to stop");
                goto cleanup;
            }
        }
    }

    // Attempt to start the service.
    if ( !StartService(
            schService,     // handle to service 
            0,              // number of arguments 
            NULL) )         // no arguments 
    {
        svc.get_log() << "StartService failed";
        //throw std::runtime_error (L"StartService failed");
        goto cleanup;
    }
    else {
        svc.get_log().write_event("Service start pending...");
        //wprintf( L"Service start pending...\n" );
    }

    // Check the status until the service is no longer start pending.
    if ( !QueryServiceStatusEx( 
            schService,
            SC_STATUS_PROCESS_INFO,
            (LPBYTE) &ssStatus,
            sizeof(SERVICE_STATUS_PROCESS),
            &dwBytesNeeded ) 
       )
    {
        svc.get_log() << "QueryServiceStatusEx failed";
        //throw std::runtime_error ("QueryServiceStatusEx failed");
        goto cleanup;
    }
 
    // Save the tick count and initial checkpoint.
    dwStartTickCount = GetTickCount();
    dwOldCheckPoint = ssStatus.dwCheckPoint;

    while ( ssStatus.dwCurrentState == SERVICE_START_PENDING )
    {
        dwWaitTime = ssStatus.dwWaitHint / 10;

        if( dwWaitTime < 1000 )
            dwWaitTime = 1000;
        else if ( dwWaitTime > 10000 )
            dwWaitTime = 10000;

        Sleep( dwWaitTime );

        if ( !QueryServiceStatusEx( 
                schService,
                SC_STATUS_PROCESS_INFO,
                (LPBYTE) &ssStatus,
                sizeof(SERVICE_STATUS_PROCESS),
                &dwBytesNeeded )
           )
        {
            svc.get_log() << "QueryServiceStatusEx failed";
            //throw std::runtime_error ("QueryServiceStatusEx failed");
            break; 
        }
 
        if ( ssStatus.dwCheckPoint > dwOldCheckPoint )
        {
            // Continue to wait and check.
            dwStartTickCount = GetTickCount();
            dwOldCheckPoint = ssStatus.dwCheckPoint;
        }
        else {
            if ( GetTickCount()-dwStartTickCount > ssStatus.dwWaitHint ) {
                // No progress made within the wait hint.
                break;
            }
        }
    } 

    // Determine whether the service is running.
    if ( ssStatus.dwCurrentState == SERVICE_RUNNING )
    {
        svc.get_log().write_event("Service started successfully");
        wprintf( L"Service started successfully\n" ); 
    }
    else { 
        svc.get_log().write_event("Service has not started");
        wprintf( L"Service has not started\n" );
    }
cleanup:
    CloseServiceHandle(schService); 
    CloseServiceHandle(schSCManager);
}


/////////////////////////////////////////////////////////////////////
//
// Goal : 
//   Stops the service.
//
void  service_stop (Service& svc)
{
    SC_HANDLE               schSCManager;
    SC_HANDLE               schService;
    SERVICE_STATUS_PROCESS  ssp;
    DWORD   dwStartTime = GetTickCount();
    DWORD   dwBytesNeeded;
    DWORD   dwTimeout = 30000; // 30-second time-out
    DWORD   dwWaitTime;

    schSCManager = OpenSCManager( 
        NULL, NULL, SC_MANAGER_ALL_ACCESS);
 
    if (NULL == schSCManager)
    {
        svc.get_log() << "OpenSCManager failed";
        //throw std::runtime_error ("OpenSCManager failed");
        return;
    }
    schService = OpenService( 
        schSCManager, svc.get_name(), 
        SERVICE_STOP | SERVICE_QUERY_STATUS );
        //| SERVICE_ENUMERATE_DEPENDENTS );
 
    if (schService == NULL) { 
        svc.get_log() << "OpenService failed";
        //throw std::runtime_error ("OpenService failed");
        CloseServiceHandle(schSCManager);
        return;
    }

    // Check if the service is not already stopped.
    if ( !QueryServiceStatusEx( 
            schService, 
            SC_STATUS_PROCESS_INFO,
            (LPBYTE)&ssp, 
            sizeof(SERVICE_STATUS_PROCESS),
            &dwBytesNeeded )
       )
    {
        svc.get_log() << "QueryServiceStatusEx failed";
        //throw std::runtime_error ("QueryServiceStatusEx failed");
        goto cleanup;
    }

    if ( ssp.dwCurrentState == SERVICE_STOPPED )
    {
        svc.get_log().write_event( "Service is already stopped" );
        goto cleanup;
    }

    // If a stop is pending, wait for it.
    while ( ssp.dwCurrentState == SERVICE_STOP_PENDING )
    {
        svc.get_log().write_event( "Service stop pending..." );

        dwWaitTime = ssp.dwWaitHint / 10;

        if( dwWaitTime < 1000 )
            dwWaitTime = 1000;
        else if ( dwWaitTime > 10000 )
            dwWaitTime = 10000;

        Sleep( dwWaitTime );

        if ( !QueryServiceStatusEx( 
                schService, 
                SC_STATUS_PROCESS_INFO,
                (LPBYTE)&ssp, 
                sizeof(SERVICE_STATUS_PROCESS),
                &dwBytesNeeded )
           )
        {
            svc.get_log() << "QueryServiceStatusEx failed";
            //throw std::runtime_error ("QueryServiceStatusEx failed");
            goto cleanup;
        }

        if ( ssp.dwCurrentState == SERVICE_STOPPED )
        {
            svc.get_log().write_event( "Service stopped successfully" );
            goto cleanup;
        }

        if ( GetTickCount() - dwStartTime > dwTimeout )
        {
            svc.get_log().write_event( "Service stop timed out" );
            goto cleanup;
        }
    }

    // If the service is running, dependencies must be stopped first.
    // Dependencies are not considered here!!!
    
    // Send a stop code to the service
    if ( !ControlService(
            schService,
            SERVICE_CONTROL_STOP,
            (LPSERVICE_STATUS) &ssp )
       )
    {
        svc.get_log() << "ControlService failed";
        //throw std::runtime_error ("ControlService failed");
        goto cleanup;
    }

    // Wait for the service to stop
    while ( ssp.dwCurrentState != SERVICE_STOPPED )
    {
        Sleep( ssp.dwWaitHint );
        if ( !QueryServiceStatusEx( 
                schService, 
                SC_STATUS_PROCESS_INFO,
                (LPBYTE)&ssp, 
                sizeof(SERVICE_STATUS_PROCESS),
                &dwBytesNeeded )
           )
        {
            svc.get_log() << "QueryServiceStatusEx failed";
            //throw std::runtime_error ("QueryServiceStatusEx failed");
            goto cleanup;
        }

        if ( ssp.dwCurrentState == SERVICE_STOPPED )
            break;

        if ( GetTickCount() - dwStartTime > dwTimeout )
        {
            svc.get_log().write_event( "Wait timed out" );
            goto cleanup;
        }
    }
    svc.get_log().write_event( "Service stopped successfully" );

cleanup:
    CloseServiceHandle(schService); 
    CloseServiceHandle(schSCManager);
}


void control_usage ()
{
    wprintf (
        L"        start\n"
        L"        stop\n"
    );
}

} /* namespace wrservice */
